import pygame
import random
import os
import heapq
import json
import numpy as np
import tkinter as tk
from tkinter import messagebox

class Game:
    def __init__(self) -> None:
        """
        初始化游戏类，包含 Pygame 初始化、音效加载、游戏设置和图案数据加载或生成。
        """
        # 初始化Pygame
        pygame.init()
        # 加载音效
        pygame.mixer.init()
        self.sounds = {
            'select': pygame.mixer.Sound('sound/select.mp3'),
            'success': pygame.mixer.Sound('sound/success.mp3'),
            'error': pygame.mixer.Sound('sound/error.mp3'),
            'win': pygame.mixer.Sound('sound/win.mp3')
        }
        pygame.mixer.music.load('sound/bgmusic.mp3')
        # 循环播放背景音乐
        pygame.mixer.music.play(-1)
        pygame.mixer.music.set_volume(0.3)  # 设置背景音乐音量
       
        # 游戏设置
        self.GRID_SIZE = 8
        self.CELL_SIZE = 60
        self.BUTTON_AREA_HEIGHT = 50
        self.WINDOW_WIDTH = self.GRID_SIZE * self.CELL_SIZE
        self.WINDOW_HEIGHT = self.GRID_SIZE * self.CELL_SIZE + self.BUTTON_AREA_HEIGHT
        self.ICON_FOLDER = "icons"
        # 创建窗口
        self.screen = pygame.display.set_mode((self.WINDOW_WIDTH, self.WINDOW_HEIGHT))
        pygame.display.set_caption("请连通公主")

        self.icons = [
            pygame.transform.scale(pygame.image.load(os.path.join(self.ICON_FOLDER, filename)), (self.CELL_SIZE, self.CELL_SIZE))
            for filename in os.listdir(self.ICON_FOLDER)
            if filename.endswith(('.png', '.jpg', '.jpeg'))
        ]

        self.matrix = np.array(self.load_pattern_data() or self.generate_pattern_matrix())
        self.save_pattern_data()
        self.RESTART_BUTTON_WIDTH = 100
        self.RESTART_BUTTON_HEIGHT = 30
        self.RESTART_BUTTON_X = (self.WINDOW_WIDTH - self.RESTART_BUTTON_WIDTH) // 2
        self.RESTART_BUTTON_Y = (self.BUTTON_AREA_HEIGHT - self.RESTART_BUTTON_HEIGHT) // 2
    

    def generate_pattern_matrix(self) -> list[list[int]]:
        """
        生成成对的图案 ID 并打乱顺序，填充到游戏矩阵中。

        返回:
            list[list[int]]: 包含图案 ID 的二维矩阵。
        """
        pattern_ids = []
        num_icons = len(self.icons)
        num_pairs = (self.GRID_SIZE * self.GRID_SIZE) // 2
        for i in range(num_pairs):
            pattern_ids.extend([i % num_icons, i % num_icons])
        # 打乱图案ID
        random.shuffle(pattern_ids)
        # 填充矩阵
        matrix = np.array([[0] * self.GRID_SIZE for _ in range(self.GRID_SIZE)])
        index = 0
        for i in range(self.GRID_SIZE):
            for j in range(self.GRID_SIZE):
                matrix[i][j] = pattern_ids[index]
                index += 1
        return matrix

    def save_pattern_data(self) -> None:
        """
        将当前游戏矩阵数据保存到 pattern_data.json 文件中。
        """
        with open('pattern_data.json', 'w') as f:
            json.dump(self.matrix.tolist(), f)

    def load_pattern_data(self) -> list[list[int]] | None:
        """
        尝试从 pattern_data.json 文件中加载游戏矩阵数据。

        返回:
            list[list[int]] | None: 若文件存在且不为空，返回矩阵数据；否则返回 None。
        """
        try:
            with open('pattern_data.json', 'r') as f:
                file_content = f.read()
                if file_content.strip() == '':
                    return None
                f.seek(0)
                return json.load(f)
        except FileNotFoundError:
            return None

    def is_valid_position(self, x: int, y: int) -> bool:
        """
        检查给定坐标是否为实际棋盘坐标或虚拟边界坐标。

        参数:
            x (int): 横坐标。
            y (int): 纵坐标。

        返回:
            bool: 若坐标有效则返回 True，否则返回 False。
        """
        return (-1 <= x <= self.GRID_SIZE) and (-1 <= y <= self.GRID_SIZE) 

    def has_block(self, x: int, y: int) -> bool:
        """
        检查实际棋盘坐标是否有方块，虚拟坐标永远无方块。

        参数:
            x (int): 横坐标。
            y (int): 纵坐标。

        返回:
            bool: 若坐标有方块则返回 True，否则返回 False。
        """
        if 0 <= x < len(self.matrix) and 0 <= y < len(self.matrix[0]): 
            return self.matrix[x][y] != 0 
        return False  # 虚拟区域永远可通行

    # 检查两个点是否可以直线连接
    def can_connect_straight(self, x1: int, y1: int, x2: int, y2: int) -> bool:
        if x1 == x2:
            start, end = sorted([y1, y2])
            for y in range(start + 1, end):
                if self.has_block(x1, y):
                    return False
            return True
        elif y1 == y2:
            start, end = sorted([x1, x2])
            for x in range(start + 1, end):
                if self.has_block(x, y1):
                    return False
            return True
        return False

    # 检查两个点是否可以一折连接
    def can_connect_one_turn(self, x1: int, y1: int, x2: int, y2: int) -> bool:
        # 检查两个可能的转折点
        points = [(x1, y2), (x2, y1)]
        for x, y in points:
            if self.is_valid_position(x, y) and not self.has_block(x, y) and self.can_connect_straight(x1, y1, x, y) and self.can_connect_straight(x, y, x2, y2):
                return True
        return False

    # 检查两个点是否可以两折连接
    def can_connect_two_turns(self, x1: int, y1: int, x2: int, y2: int) -> bool:
        # 扫描x1同一水平方向的所有点C
        for y in range(-1, self.GRID_SIZE + 1):
            if y != y1 and self.is_valid_position(x1, y) and not self.has_block(x1, y) and self.can_connect_straight(x1, y1, x1, y) and self.can_connect_one_turn(x1, y, x2, y2):
                return True
        # 扫描x1同一垂直方向的所有点C
        for x in range(-1, self.GRID_SIZE + 1):
            if x != x1 and self.is_valid_position(x, y1) and not self.has_block(x, y1) and self.can_connect_straight(x1, y1, x, y1) and self.can_connect_one_turn(x, y1, x2, y2):
                return True
        return False

    # 检查两个点是否可以连接
    def can_connect(self, x1: int, y1: int, x2: int, y2: int) -> bool:
        if not self.is_valid_position(x1, y1) or not self.is_valid_position(x2, y2):
            return False
        if self.matrix[x1][y1] != self.matrix[x2][y2] or self.matrix[x1][y1] == 0:
            return False
        # 提前检查是否为同一位置
        if (x1, y1) == (x2, y2):
            return False
        if self.can_connect_straight(x1, y1, x2, y2):
            return True
        if self.can_connect_one_turn(x1, y1, x2, y2):
            return True
        if self.can_connect_two_turns(x1, y1, x2, y2):
            return True
        return False

    def is_no_solution(self) -> bool:
        """
        检查当前游戏矩阵是否无解。

        返回:
            bool: 若无解则返回 True，否则返回 False。
        """
        for i in range(self.GRID_SIZE):
            for j in range(self.GRID_SIZE):
                if self.matrix[i][j] != 0:
                    for m in range(self.GRID_SIZE):
                        for n in range(self.GRID_SIZE):
                            if (i, j) != (m, n) and self.matrix[m][n] == self.matrix[i][j] and self.can_connect(i, j, m, n):
                                return False
        return True

    def rearrange_matrix(self) -> None:
        """
        重新排列游戏矩阵中非零元素的位置，并保存新的矩阵数据。
        """
        non_zero_elements = []
        for i in range(self.GRID_SIZE):
            for j in range(self.GRID_SIZE):
                if self.matrix[i][j] != 0:
                    non_zero_elements.append(self.matrix[i][j])
        random.shuffle(non_zero_elements)
        self.matrix = np.array([[0] * self.GRID_SIZE for _ in range(self.GRID_SIZE)])
        index = 0
        for i in range(self.GRID_SIZE):
            for j in range(self.GRID_SIZE):
                if index < len(non_zero_elements):
                    self.matrix[i][j] = non_zero_elements[index]
                    index += 1
        self.save_pattern_data()

    def run(self) -> None:
        """
        启动游戏主循环，处理游戏事件和绘制游戏界面。
        """
        # 确保 pygame 在作用域内可用
        import pygame
        print(f"游戏开始运行：" )
        print(f"{self.matrix}")
        selected = []
        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    x, y = event.pos
                    if self.RESTART_BUTTON_X <= x <= self.RESTART_BUTTON_X + self.RESTART_BUTTON_WIDTH and self.RESTART_BUTTON_Y <= y <= self.RESTART_BUTTON_Y + self.RESTART_BUTTON_HEIGHT:
                        self.matrix = np.array(self.generate_pattern_matrix())
                        self.save_pattern_data()
                    try:
                        col = x // self.CELL_SIZE
                        row = (y - self.BUTTON_AREA_HEIGHT) // self.CELL_SIZE
                        if 0 <= row < self.GRID_SIZE and 0 <= col < self.GRID_SIZE and self.matrix[row][col] != 0:
                            if len(selected) < 2:
                                self.sounds['select'].play()
                                selected.append((row, col))
                                if len(selected) == 2:
                                    r1, c1 = selected[0]
                                    r2, c2 = selected[1]
                                    # 提前检查坐标范围
                                    if 0 <= r1 < self.GRID_SIZE and 0 <= c1 < self.GRID_SIZE and 0 <= r2 < self.GRID_SIZE and 0 <= c2 < self.GRID_SIZE:
                                        if self.matrix[r1][c1] == self.matrix[r2][c2] and self.can_connect(r1, c1, r2, c2):
                                            print(f"准备消除图标: ({r1}, {c1}) 和 ({r2}, {c2})")
                                            self.sounds['success'].play()
                                            self.matrix[r1][c1] = 0
                                            self.matrix[r2][c2] = 0
                                            self.save_pattern_data()
                                            print(f"图标消除成功，当前矩阵状态:")
                                            print(f"{self.matrix}")
                                            if np.all(self.matrix == 0):
                                                self.sounds['win'].play()
                                                root = tk.Tk()
                                                root.withdraw()
                                                result = messagebox.askyesno('游戏结束', '是否重新开始？')
                                                if result:
                                                    self.matrix = np.array(self.generate_pattern_matrix())
                                                    self.save_pattern_data()
                                            if self.is_no_solution():
                                                self.rearrange_matrix()
                                            selected = []
                                        else:
                                            self.sounds['error'].play()
                                            selected = []
                                    else:
                                        print('错误：选中的坐标超出范围，请重新选择。')
                    except IndexError:
                        print('错误：选中了非相邻方块，请重新选择。')

            # 填充背景色
            self.screen.fill((255, 240, 220))

            # 绘制按钮区域背景
            pygame.draw.rect(self.screen, (255, 228, 196), (0, 0, self.WINDOW_WIDTH, self.BUTTON_AREA_HEIGHT))

            # 绘制重新开始按钮
            pygame.draw.rect(self.screen, (255, 165, 0), (self.RESTART_BUTTON_X, self.RESTART_BUTTON_Y, self.RESTART_BUTTON_WIDTH, self.RESTART_BUTTON_HEIGHT))
            font = pygame.font.SysFont('SimHei', 16)
            text = font.render("重新开始", True, (255, 255, 255))
            text_rect = text.get_rect(center=(self.RESTART_BUTTON_X + self.RESTART_BUTTON_WIDTH // 2, self.RESTART_BUTTON_Y + self.RESTART_BUTTON_HEIGHT // 2))
            self.screen.blit(text, text_rect)

            # 绘制网格线
            for i in range(self.GRID_SIZE + 1):
                pygame.draw.line(self.screen, (205, 133, 63), (i * self.CELL_SIZE, self.BUTTON_AREA_HEIGHT), (i * self.CELL_SIZE, self.WINDOW_HEIGHT))
                pygame.draw.line(self.screen, (205, 133, 63), (0, i * self.CELL_SIZE + self.BUTTON_AREA_HEIGHT), (self.WINDOW_WIDTH, i * self.CELL_SIZE + self.BUTTON_AREA_HEIGHT))

            # 绘制图案
            for i in range(self.GRID_SIZE):
                for j in range(self.GRID_SIZE):
                    if self.matrix[i][j] != 0:
                        self.screen.blit(self.icons[self.matrix[i][j]], (j * self.CELL_SIZE, i * self.CELL_SIZE + self.BUTTON_AREA_HEIGHT))

            # 绘制高亮边框
            for row, col in selected:
                pygame.draw.rect(self.screen, (0, 255, 0), (col * self.CELL_SIZE + 1, row * self.CELL_SIZE + self.BUTTON_AREA_HEIGHT + 1, self.CELL_SIZE - 2, self.CELL_SIZE - 2), 3)

            # 更新显示
            pygame.display.flip()
if __name__ == '__main__':
    game = Game()
    game.run()
